﻿//-----------------------------------------------------------------------
// <copyright file="SimpleGraph.cs" company="Redmist">
//     Copyright (c) Damien T Smith. All rights reserved.
// </copyright>
// <author>Damien Smith</author>
//-----------------------------------------------------------------------

namespace Redmist.UsageMonitor.CustomControls
{
    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Windows.Forms;

    /// <summary>A simple histogram control which can display a single Y-value or two Y-values on the graph.</summary>
    public partial class SimpleGraph : UserControl
    {
        /// <summary>Represents the data source of the primary graph.</summary>
        private int[] data;

        /// <summary>Represents the data source of the secondary graph.</summary>
        private int[] data2;

        /// <summary>Indicates whether the histogram is displaying both primary and secondary graphs in combined mode.</summary>
        private bool combinedMode;

        /// <summary>The maximum Y-value (range) for the graph.</summary>
        private int maximumValue;

        /// <summary>Initializes a new instance of the SimpleGraph class.</summary>
        public SimpleGraph()
        {
            this.InitializeComponent();
            this.Size = new Size(170, 100);
            this.PrimaryGraphLeftColor = Color.FromArgb(255, 90, 143, 255);
            this.PrimaryGraphRightColor = Color.FromArgb(255, 20, 62, 81);
            this.SecondaryGraphLeftColor = Color.FromArgb(255, 136, 229, 189);
            this.SecondaryGraphRightColor = Color.FromArgb(255, 41, 114, 31);
            this.BackBottomColor = Color.White;
            this.BackTopColor = Color.LightGray;
            this.MaximumValue = 100;
            this.pictureBox.MouseMove += new MouseEventHandler(this.PictureBox_MouseMove);
        }

        /// <summary>Gets or sets the left color of the primary graph bars gradient.</summary>
        [DefaultValue(typeof(Color), "90, 143, 255")]
        public Color PrimaryGraphLeftColor { get; set; }

        /// <summary>Gets or sets the right color of the primary graph bars gradient.</summary>
        [DefaultValue(typeof(Color), "20, 62, 81")]
        public Color PrimaryGraphRightColor { get; set; }

        /// <summary>Gets or sets the left color of the secondary graph bars gradient.</summary>
        [DefaultValue(typeof(Color), "136, 229, 189")]
        public Color SecondaryGraphLeftColor { get; set; }

        /// <summary>Gets or sets the right color of the secondary graph bars gradient.</summary>
        [DefaultValue(typeof(Color), "41, 114, 31")]
        public Color SecondaryGraphRightColor { get; set; }

        /// <summary>Gets or sets the bottom color of the graph background gradient.</summary>
        [DefaultValue(typeof(Color), "White")]
        public Color BackBottomColor { get; set; }

        /// <summary>Gets or sets the top color of the graph background gradient.</summary>
        [DefaultValue(typeof(Color), "LightGray")]
        public Color BackTopColor { get; set; }

        /// <summary>Gets or sets the maximum Y value for the graph.</summary>
        [DefaultValue(100)]
        public int MaximumValue
        {
            get
            {
                return this.maximumValue;
            }

            set
            {
                this.maximumValue = value;
                this.PerformUpdate();
            }
        }

        /// <summary>Gets or sets the data array for the primary graph.</summary>
        public int[] Data
        {
            get
            {
                return this.data;
            }

            set
            {
                this.data = value;
                this.PerformUpdate();
            }
        }

        /// <summary>Gets or sets the data array for the primary graph.</summary>
        public int[] Data2
        {
            get
            {
                return this.data2;
            }

            set
            {
                this.data2 = value;
                this.PerformUpdate();
            }
        }

        /// <summary>Gets or sets a value indicating whether the primary and secondary graphs should be shown combined as a single graph.</summary>
        public bool CombinedMode
        {
            get
            {
                return this.combinedMode;
            }

            set
            {
                this.combinedMode = value;
                this.PerformUpdate();
            }
        }

        /// <summary>Sets the size of the main PictureBox to the same size as the client control.</summary>
        /// <param name="e">A System.EventArgs representing the event arguments.</param>
        protected override void OnResize(EventArgs e)
        {
            this.pictureBox.Size = new Size(this.ClientRectangle.Width, this.ClientRectangle.Height);
            this.UpdateGraph();
            base.OnResize(e);
        }

        /// <summary>Handles the MouseMove event for the picture box by raising the MouseMove event for the SimpleGraph control.</summary>
        /// <param name="sender">A System.Object representing the sending object.</param>
        /// <param name="e">A System.EventArgs representing the event arguments.</param>
        private void PictureBox_MouseMove(object sender, MouseEventArgs e)
        {
            this.OnMouseMove(e);
        }

        /// <summary>Checks if the current data is valid and if so, performs an update of the graph.</summary>
        private void PerformUpdate()
        {
            if (this.combinedMode)
            {
                if (this.data == null || this.data2 == null || this.data.Length != this.data2.Length)
                {
                    return;
                }
            }
            else
            {
                if (this.data == null)
                {
                    return;
                }
            }

            this.UpdateGraph();
        }

        /// <summary>Redraws the graph based on the current source data.</summary>
        private void UpdateGraph()
        {
            Bitmap bitmap = new Bitmap(this.pictureBox.Width, this.pictureBox.Height);
            using (Graphics graphics = Graphics.FromImage(bitmap))
            {
                this.DrawBackground(graphics);
                if (this.data != null && this.data.Length > 0 && this.MaximumValue > 0)
                {
                    this.ScaleAndRotateGraph(graphics);
                    this.DrawHistogram(graphics);
                    graphics.ResetTransform();
                }

                this.DrawBorder(graphics);
            }

            this.pictureBox.Image = bitmap;
        }

        /// <summary>Draws a border around the graph area.</summary>
        /// <param name="graphics">A System.Drawing.Graphics representing the GDI+ drawing surface.</param>
        private void DrawBorder(Graphics graphics)
        {
            graphics.DrawRectangle(Pens.Gray, 0, 0, this.ClientRectangle.Width - 1, this.ClientRectangle.Height - 1);
        }

        /// <summary>Draws the histogram bars on the graph.</summary>
        /// <param name="graphics">A System.Drawing.Graphics representing the GDI+ drawing surface.</param>
        private void DrawHistogram(Graphics graphics)
        {
            for (int i = 0; i <= this.data.Length - 1; i++)
            {
                Rectangle barBorderRectangle = new Rectangle(i, 0, 1, this.data[i]);
                float startX = 0f;
                float startY = barBorderRectangle.Height / 2f;
                float endX = (float)barBorderRectangle.Width;
                float endY = startY;
                using (LinearGradientBrush barBrush = new LinearGradientBrush(new PointF(startX, startY), new PointF(endX, endY), this.PrimaryGraphLeftColor, this.PrimaryGraphRightColor))
                {
                    graphics.FillRectangle(barBrush, barBorderRectangle);
                }

                if (this.combinedMode && this.data2 != null)
                {
                    Rectangle bar2BorderRectangle = new Rectangle(i, this.data[i], 1, this.data2[i]);
                    startY = bar2BorderRectangle.Height / 2f;
                    endY = startY;
                    using (LinearGradientBrush bar2Brush = new LinearGradientBrush(new PointF(startX, startY), new PointF(endX, endY), this.SecondaryGraphLeftColor, this.SecondaryGraphRightColor))
                    {
                        graphics.FillRectangle(bar2Brush, bar2BorderRectangle);
                    }

                    barBorderRectangle.Height = barBorderRectangle.Height + bar2BorderRectangle.Height;
                }

                if (this.ClientRectangle.Width / this.data.Length > 1.3)
                {
                    using (Pen barBorderPen = new Pen(Color.LightGray, 0))
                    {
                        graphics.DrawRectangle(barBorderPen, barBorderRectangle);
                    }
                }
            }
        }

        /// <summary>Sets the scaling and rotation of the graph so that the histogram automatically fits the client area and the X-axis is at the bottom of the graph.</summary>
        /// <param name="graphics">A System.Drawing.Graphics representing the GDI+ drawing surface.</param>
        private void ScaleAndRotateGraph(Graphics graphics)
        {
            float scaleX = (float)this.ClientRectangle.Width / this.data.Length;
            float scaleY = (float)-this.ClientRectangle.Height / this.MaximumValue;
            graphics.ScaleTransform(scaleX, scaleY, MatrixOrder.Append);
            graphics.TranslateTransform(0, this.ClientRectangle.Height, MatrixOrder.Append);
        }

        /// <summary>Draws the gradient background for the graph.</summary>
        /// <param name="graphics">A System.Drawing.Graphics representing the GDI+ drawing surface.</param>
        private void DrawBackground(Graphics graphics)
        {
            float startX = this.ClientRectangle.Width / 2f;
            float startY = this.ClientRectangle.Height;
            float endX = startX;
            float endY = 0f;
            PointF startPoint = new PointF(startX, startY);
            PointF endPoint = new PointF(endX, endY);
            using (LinearGradientBrush backBrush = new LinearGradientBrush(startPoint, endPoint, this.BackBottomColor, this.BackTopColor))
            {
                graphics.FillRectangle(backBrush, this.ClientRectangle);
            }
        }
    }
}
